package com.karaoke.android.player.videocache;

import android.text.TextUtils;

import com.karaoke.android.player.videocache.sourcestorage.SourceInfoStorage;
import com.karaoke.android.player.videocache.sourcestorage.SourceInfoStorageFactory;

import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.util.concurrent.TimeUnit;

import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.Response;

import static com.karaoke.android.player.videocache.Preconditions.checkNotNull;
import static com.karaoke.android.player.videocache.ProxyCacheUtils.DEFAULT_BUFFER_SIZE;
import static java.net.HttpURLConnection.HTTP_OK;
import static java.net.HttpURLConnection.HTTP_PARTIAL;

/**
 * Created by allen on 2017/5/2.
 */

public class HttpUrlSource implements Source
{

    private final SourceInfoStorage sourceInfoStorage;
    private       SourceInfo        sourceInfo;
    private       InputStream       inputStream;

    private static final int MAX_REDIRECTS = 5;

    private OkHttpClient httpClient = new OkHttpClient().newBuilder().connectTimeout(10, TimeUnit
            .SECONDS).readTimeout(30, TimeUnit.SECONDS).writeTimeout(10, TimeUnit.SECONDS).build();

    public HttpUrlSource(String url)
    {
        this(url, SourceInfoStorageFactory.newEmptySourceInfoStorage());
    }

    public HttpUrlSource(String url, SourceInfoStorage sourceInfoStorage)
    {
        this.sourceInfoStorage = checkNotNull(sourceInfoStorage);
        SourceInfo sourceInfo = sourceInfoStorage.get(url);
        this.sourceInfo = sourceInfo != null ? sourceInfo :
                new SourceInfo(url, Integer.MIN_VALUE, ProxyCacheUtils.getSupposablyMime(url));
    }

    public HttpUrlSource(HttpUrlSource source)
    {
        this.sourceInfo = source.sourceInfo;
        this.sourceInfoStorage = source.sourceInfoStorage;
    }

    @Override
    public void open(long offset) throws ProxyCacheException
    {
        try
        {
            Response response = openConnection((int) offset, -1);
            String   mime     = response.header("Content-Type");
            int      length   = readSourceAvailableBytes(response, (int) offset);
            inputStream = new BufferedInputStream(response.body().byteStream(),
                    DEFAULT_BUFFER_SIZE);
            this.sourceInfo = new SourceInfo(sourceInfo.url, length, mime);
            this.sourceInfoStorage.put(sourceInfo.url, sourceInfo);
        }
        catch (IOException e)
        {
        }
    }

    private int readSourceAvailableBytes(Response response, int offset) throws IOException
    {
        int responseCode  = response.code();
        int contentLength = (int) response.body().contentLength();
        return responseCode == HTTP_OK ? contentLength
                : (int) (responseCode == HTTP_PARTIAL ? contentLength + offset : sourceInfo.length);
    }

    @Override
    public long length() throws ProxyCacheException
    {
        if (sourceInfo.length == Integer.MIN_VALUE)
        {
            fetchContentInfo();
        }
        return sourceInfo.length;
    }

    private void fetchContentInfo() throws ProxyCacheException
    {
        Response response = null;
        try
        {
            response = openConnectionForHeader(10000);
            if (response == null || !response.isSuccessful())
            {
                throw new ProxyCacheException("Fail to fetchContentInfo: " + sourceInfo.url);
            }
            long   length = (int) response.body().contentLength();
            String mime   = response.header("Content-Type");
            inputStream = response.body().byteStream();
            this.sourceInfo = new SourceInfo(sourceInfo.url, length, mime);
            this.sourceInfoStorage.put(sourceInfo.url, sourceInfo);
        }
        catch (IOException e)
        {
        }
        finally
        {
        }
    }

    private Response openConnectionForHeader(int timeout) throws IOException, ProxyCacheException
    {

        Response response;
        boolean  isRedirect    = false;
        String   newUrl        = sourceInfo.url;
        int      redirectCount = 0;
        do
        {
            Request request = new Request.Builder()
                    .head()
                    .url(newUrl)
                    .build();
            response = httpClient.newCall(request).execute();
            if (response.isRedirect())
            {
                newUrl = response.header("Location");
                isRedirect = response.isRedirect();
                redirectCount++;
            }
            if (redirectCount > MAX_REDIRECTS)
            {
                throw new ProxyCacheException("Too many redirects: " + redirectCount);
            }
        } while (isRedirect);

        return response;
    }


    @Override
    public int read(byte[] buffer) throws ProxyCacheException
    {
        if (inputStream == null)
        {
            throw new ProxyCacheException("Error reading data from " + sourceInfo.url + ": " +
                    "connection is absent!");
        }
        try
        {
            return inputStream.read(buffer, 0, buffer.length);
        }
        catch (InterruptedIOException e)
        {
            throw new InterruptedProxyCacheException("Reading source " + sourceInfo.url + " is " +
                    "interrupted", e);
        }
        catch (IOException e)
        {
            throw new ProxyCacheException("Error reading data from " + sourceInfo.url, e);
        }
    }

    @Override
    public void close() throws ProxyCacheException
    {
        ProxyCacheUtils.close(inputStream);
    }

    private Response openConnection(int offset, int timeout) throws IOException, ProxyCacheException
    {
        Response response;
        boolean  isRedirect    = false;
        String   newUrl        = sourceInfo.url;
        int      redirectCount = 0;
        do
        {
            Request.Builder requestBuilder = new Request.Builder();
            requestBuilder.get();
            requestBuilder.url(newUrl);
            if (offset > 0)
            {
                requestBuilder.addHeader("Range", "bytes=" + offset + "-");
            }
            response = httpClient.newCall(requestBuilder.build()).execute();
            if (response.isRedirect())
            {
                newUrl = response.header("Location");
                isRedirect = response.isRedirect();
                redirectCount++;
            }
            if (redirectCount > MAX_REDIRECTS)
            {
                throw new ProxyCacheException("Too many redirects: " + redirectCount);
            }
        } while (isRedirect);

        return response;
    }

    public synchronized String getMime() throws ProxyCacheException
    {
        if (TextUtils.isEmpty(sourceInfo.mime))
        {
            fetchContentInfo();
        }
        return sourceInfo.mime;
    }

    public String getUrl()
    {
        return sourceInfo.url;
    }

    @Override
    public String toString()
    {
        return "HttpUrlSource{sourceInfo='" + sourceInfo + "}";
    }
}
